using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using System.IO;

#if UNITY_EDITOR
using UnityEditor;
#endif

public class ObjectPoolManager : SingleMono<ObjectPoolManager>
{

    Dictionary<string, Queue<GameObject>> pool;
    Dictionary<string, HashSet<BasePoolElement>> activePool;
    public Dictionary<string, Queue<GameObject>> Pool => pool;
    public Dictionary<string, HashSet<BasePoolElement>> ActivePool => activePool;
    public bool isEditorMode = false;
    public bool isDebug = false;
    public readonly string resourcePath = "Prefab/";
    public readonly string assetPath = "Assets/WResources/Prefab/";
    public AssetBundle defaultBundle;
    public Dictionary<string, AssetBundle> bundles;

    private void Awake()
    {
        base.Awake();
        Init();
        InitSceneLoad();
    }
    void Init()
    {
        pool = new Dictionary<string, Queue<GameObject>>();
        activePool = new Dictionary<string, HashSet<BasePoolElement>>();
        transform.position = Vector3.zero;
    }
    void InitSceneLoad()
    {
        SceneManager.Instance.StopAllCoroutines();
        SceneManager.Instance.onLoadingStart += a => ClearAllPool();
    }

    public void InitBundles(AssetBundle defaultBundle, Dictionary<string, AssetBundle> bundles)
    {
        this.defaultBundle = defaultBundle;
        this.bundles = bundles;
    }
    public T DePool<T>(string name, bool isUseResource = false, string path = null) where T : BasePoolElement
    {
        if (pool.ContainsKey(name) && pool[name] != null && pool[name].Count > 0)
        {
            Debug.Log($"depool from  pool:{path}/{name}");
            GameObject go = pool[name].Dequeue();
            BasePoolElement script = go.GetComponent<BasePoolElement>();
            T poolElement = null;
            if (!(script is T))
            {
                DestroyImmediate(script);
                poolElement = go.AddComponent<T>();
            }
            else
            {
                poolElement = script as T;
            }
            poolElement.DePool(name);
            EnActivPool(poolElement);
            return poolElement;
        }
        else
        {
            Debug.Log($"depool from  Instantiate:{path}/{name}");
            GameObject go = PoolInstantiate(name, isUseResource, path);
            T poolElement = go.AddComponent<T>();
            poolElement.DePool(name);
            EnActivPool(poolElement);
            return poolElement;
        }
    }

    void EnActivPool<T>(T element) where T : BasePoolElement
    {
        if (!activePool.ContainsKey(element.PoolName))
        {
            activePool.Add(element.PoolName, new HashSet<BasePoolElement>());
        }
        activePool[element.PoolName].Add(element);
    }

    public void DeActivePool<T>(T element) where T : BasePoolElement
    {
        if (activePool.ContainsKey(element.PoolName))
            activePool[element.PoolName].Remove(element);
    }

    public void EnPool(BasePoolElement element)
    {
        Debug.Log($"enpool:{element.PoolName}");
        if (element == null || element.gameObject == null || element.IsInPool) return;


        if (!pool.ContainsKey(element.PoolName))
        {
            pool.Add(element.PoolName, new Queue<GameObject>());
        }
        element.EnPool();
        pool[element.PoolName].Enqueue(element.gameObject);
        DeActivePool(element);
    }
    GameObject PoolInstantiate(string name, bool isUseResource = false, string path = null)
    {
        GameObject go = null;

        if (isUseResource)
        {
            if (path != null)
            {
                go = Resources.Load<GameObject>($"{path}/{name}");
            }
            else
            {
                go = Resources.Load<GameObject>(name);
            }
        }
        else if (isEditorMode && Application.isEditor)
        {
#if UNITY_EDITOR
            go = AssetDatabase.LoadAssetAtPath<GameObject>(Path.Combine(assetPath, path) + "/" + name + ".prefab");
            if (go == null)
            {
                Debug.LogError(Path.Combine(assetPath, path) + "/" + name + ".prefab");
            }
#endif
        }
        else
        {
            if (path == null && defaultBundle != null)
            {
                go = defaultBundle.LoadAsset<GameObject>(name);
            }
            else
            {
                path = resourcePath.TrimEnd('/') + "_" + path;
                go = bundles[path].LoadAsset<GameObject>(name);
            }
        }
        if (go == null)
        {
            Debug.LogError($"can not find {name} in {path}");
        }
        return Instantiate<GameObject>(go);
    }

    public void ClearAllPool()
    {
        if (pool != null)
        {
            foreach (var item in pool.Values)
            {
                while (item.Count > 0)
                {
                    GameObject obj = item.Dequeue();
                    if (obj != null)
                    {
                        Destroy(obj);
                    }
                }
                item.Clear();
            }
            pool.Clear();
        }
        if (activePool != null)
        {
            foreach (var item in activePool.Values)
            {
                foreach (var sitem in item)
                {
                    if (sitem != null && sitem.gameObject != null)
                    {
                        GameObject obj = sitem.gameObject;
                        Destroy(obj);
                    }

                }
                item.Clear();
            }
            activePool.Clear();
        }
    }

    public void ClearPool(string poolName)
    {
        if (pool != null && pool.ContainsKey(poolName))
        {
            while (pool[poolName].Count > 0)
            {
                GameObject obj = pool[poolName].Dequeue();
                if (obj != null)
                {
                    Destroy(obj);
                }
            }
            pool[poolName].Clear();
        }
        if (activePool != null && activePool.ContainsKey(poolName))
        {
            foreach (var sitem in activePool[poolName])
            {
                if (sitem != null && sitem.gameObject != null)
                {
                    GameObject obj = sitem.gameObject;
                    Destroy(obj);
                }
            }
            activePool[poolName].Clear();
        }
    }

    public class Debug : DebugHelper
    {
        public static void LogError(object s)
        {
            if (Instance.isDebug)
                UnityEngine.Debug.LogError(s);
        }
        public static void Log(object s)
        {
            if (Instance.isDebug)
                UnityEngine.Debug.Log(s);
        }
    }
}

public abstract class BasePoolElement : MonoBehaviour
{

    bool isInPool;
    string poolName;


    public virtual void PoolInit()
    {

    }
    public bool IsInPool => isInPool;
    public string PoolName => poolName;
    public virtual void EnPool()
    {
        isInPool = true;
        this.enabled = false;
        gameObject.SetActive(false);
    }

    public virtual void DePool(string poolName)
    {
        this.poolName = poolName;
        isInPool = false;
        this.enabled = true;
        gameObject.SetActive(true);
        PoolInit();
    }

    void OnDestroy()
    {
        if (!isInPool && ObjectPoolManager.Instance != null)
            ObjectPoolManager.Instance.DeActivePool(this);
    }



    class Time
    {
        public float deltaTime => UnityEngine.Time.deltaTime;
        public float fixedDeltaTime => UnityEngine.Time.fixedDeltaTime;
    }

}

